/**
 * Copyright (C) Inexas 2009, All rights reserved.
 *
 * @author keith
 */
package com.inexas.cu.server.pipe;

import java.util.*;
import com.inexas.cu.client.pipe.*;

/**
 * A registry for GAE RPC handlers.
 * 
 * Every request needs at least one service provider to handle the request.
 */
public class ServiceRegistry {
	// ?todo Implement removal of providers/observers - see how this is done in
	// HandlerManager

	public interface ServiceProvider {
		AbstractResponse service(AbstractRequest request);
	}

	public interface ServiceObserver {
		void handle(AbstractRequest request, AbstractResponse response);
	}

	private static class Group {
		public ServiceProvider provider;
		public List<ServiceObserver> observers;

		public void add(ServiceObserver observer) {
			if(observers == null) {
				observers = new ArrayList<ServiceObserver>();
			}
			observers.add(observer);
		}
	}

	public class InvalidRegistrationException extends RuntimeException {
		private static final long serialVersionUID = -9160315336892233578L;

		public InvalidRegistrationException(String message) {
			super(message);
		}
	}

	private final Map<Class<? extends AbstractRequest>, Group> groupMap;

	public ServiceRegistry() {
		groupMap = new HashMap<Class<? extends AbstractRequest>, Group>();
	}

	public void register(Class<? extends AbstractRequest> piped, ServiceProvider provider) {
		if(provider == null) {
			throw new InvalidRegistrationException("Null service provider");
		}

		final Group group = groupMap.get(piped);
		if(group != null) {
			if(group.provider != null) {
				throw new InvalidRegistrationException( //
				        "Provider already registered for: " + piped.getName());
			}
			// An observer for this request has already been added
			group.provider = provider;
		} else {
			final ServiceRegistry.Group newGroup = new Group();
			newGroup.provider = provider;
			groupMap.put(piped, newGroup);
		}
	}

	public void register(Class<? extends AbstractRequest> piped, ServiceObserver observer) {
		if(observer == null) {
			throw new InvalidRegistrationException("Null observer");
		}

		final ServiceRegistry.Group group = groupMap.get(piped);
		if(group == null) {
			final ServiceRegistry.Group newGroup = new Group();
			newGroup.add(observer);
			groupMap.put(piped, newGroup);
		} else {
			group.add(observer);
		}
	}

	public AbstractResponse fire(AbstractRequest request) {
		final AbstractResponse result;

		// Find a group for the provider...
		final String key = request.getClass().getName();
		final Group group = getGroup(request.getClass(), true);
		if(group == null) {
			throw new InvalidRegistrationException("No provider registered for: " + key);
		}
		result = group.provider.service(request);

		// Broadcast to any observers...
		final Group passiveGroup = getGroup(request.getClass(), false);
		if(passiveGroup != null) {
			final List<ServiceObserver> passives = passiveGroup.observers;
			final int size = passives.size();
			for(int i = 0; i < size; i++) {
				passives.get(i).handle(request, result);
			}
		}

		return result;
	}

	private Group getGroup(Class<?> currentClass, boolean active) {
		// ?todo This could be made more efficient by 'remembering'
		// which group is returned so that we only need to go
		// recursive once for each request
		Group result;
		if(AbstractRequest.class.isAssignableFrom(currentClass)) {
			result = groupMap.get(currentClass);
			if(result == null || //
			        (active ? result.provider == null : result.observers == null)) {
				result = getGroup(currentClass.getSuperclass(), active);
			}
		} else {
			result = null;
		}
		return result;
	}

}
